package com.xyq.friend.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xyq.common.core.constants.Constants;
import com.github.pagehelper.PageHelper;
import com.xyq.common.core.constants.CacheConstants;
import com.xyq.common.core.enums.ExamListType;
import com.xyq.common.core.enums.ResultCode;
import com.xyq.common.core.exception.ServiceException;
import com.xyq.common.redis.service.RedisService;
import com.xyq.friend.domain.exam.Exam;
import com.xyq.friend.domain.exam.dto.ExamQueryDTO;
import com.xyq.friend.domain.exam.ExamQuestion;
import com.xyq.friend.domain.exam.dto.ExamRankDTO;
import com.xyq.friend.domain.exam.vo.ExamRankVO;
import com.xyq.friend.domain.exam.vo.ExamVO;
import com.xyq.friend.domain.user.UserExam;
import com.xyq.friend.domain.user.UserScore;
import com.xyq.friend.domain.user.vo.UserVO;
import com.xyq.friend.mapper.ExamMapper;
import com.xyq.friend.mapper.ExamQuestionMapper;
import com.xyq.friend.mapper.UserExamMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class ExamCacheManager {

    @Autowired
    private ExamMapper examMapper;


    @Autowired
    private RedisService redisService;

    @Autowired
    private UserExamMapper userExamMapper ;

    @Autowired
    private ExamQuestionMapper examQuestionMapper ;

    @Autowired
    private UserCacheManager userCacheManager ;


    // 获取列表的总长度
    public Long getListSize(Integer examListType , Long userId) {
        String examListKey = getExamListKey(examListType , userId);
        return redisService.getListSize(examListKey);
    }
    // 获取竞赛题目列表数量
    public Long getExamQuestionListSize(Long examId) {
        String examQuestionListKey = getExamQuestionListKey(examId);
        return redisService.getListSize(examQuestionListKey);
    }
    // 获取竞赛题目列表的key
    public String getExamQuestionListKey(Long examId){
        return CacheConstants.EXAM_QUESTION_LIST+examId ;
    }

    public void addUserExamCaChe(Long examId , Long userId){
       // 获取redis里的key
        String userExamListKey = getUserExamListKey(userId);
        // 插入redis中
        redisService.leftPushForList(userExamListKey , examId)  ;
    }


    public List<ExamVO> getExamVOList(ExamQueryDTO examQueryDTO , Long userId) {
        // 计算下标处理分页
        int start = (examQueryDTO.getPageNum() - 1) * examQueryDTO.getPageSize();
        int end = start + examQueryDTO.getPageSize() - 1; //下标需要 -1
       // 判断是要 哪个竞赛列表
        String examListKey = getExamListKey(examQueryDTO.getType(), userId);
        // 获取列表 redis里的列表存储的的是竞赛id
        List<Long> examIdList = redisService.getCacheListByRange(examListKey, start, end, Long.class);
        // 通过竞赛id , 获取对应的竞赛
        List<ExamVO> examVOList = assembleExamVOList(examIdList);

        // 如果获取到的列表里有空的 , 就表示数据有问题 ,
        // 而从 MySQL数据库里查
        if (CollectionUtil.isEmpty(examVOList)) {
            //说明redis中数据可能有问题 从数据库中查数据并且重新刷新缓存
            examVOList = getExamListByDB(examQueryDTO , userId); //从数据库中获取数据
            // 查完后同时 refreshCache 刷新一下redis缓存
            refreshCache(examQueryDTO.getType() , userId);
        }
        return examVOList;
    }


    //刷新缓存逻辑
    public void refreshCache(Integer examListType , Long userId) {
        List<Exam> examList = new ArrayList<>();
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(examListType)) {
            //查询未完赛的竞赛列表
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                    .gt(Exam::getEndTime, LocalDateTime.now())
                    .eq(Exam::getStatus, Constants.TRUE)
                    .orderByDesc(Exam::getCreateTime));
        } else if (ExamListType.EXAM_HISTORY_LIST.getValue().equals(examListType)) {
            //查询历史竞赛
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                    .le(Exam::getEndTime, LocalDateTime.now())
                    .eq(Exam::getStatus, Constants.TRUE)
                    .orderByDesc(Exam::getCreateTime));
        }else  if(ExamListType.USER_EXAM_LIST.getValue().equals(examListType)){
            // 如果是 我的竞赛
            List<ExamVO> examVOList = userExamMapper.selectUserExamList(userId);
            examList =  BeanUtil.copyToList(examVOList , Exam.class);
        }
        if (CollectionUtil.isEmpty(examList)) {
            return;
        }

        Map<String, Exam> examMap = new HashMap<>();
        List<Long> examIdList = new ArrayList<>();
        for (Exam exam : examList) {
            examMap.put(getDetailKey(exam.getExamId()), exam);
            examIdList.add(exam.getExamId());
        }
        redisService.multiSet(examMap);  //刷新详情缓存
        redisService.deleteObject(getExamListKey(examListType , userId)); // 先删除
        redisService.rightPushAll(getExamListKey(examListType, userId), examIdList);      //刷新列表缓存
    }


       // 通过MySQL的 接口来获取竞赛列表
    private List<ExamVO> getExamListByDB(ExamQueryDTO examQueryDTO , Long userId) {
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        //查询C端的竞赛列表
        // 如果 是我的竞赛列表
        if(ExamListType.USER_EXAM_LIST.getValue().equals(examQueryDTO.getType())){
            return userExamMapper.selectUserExamList(userId);
        }else{
            //查询竞赛列表
            //查询C端的竞赛列表
            return examMapper.selectExamList(examQueryDTO);
        }
    }

    private List<ExamVO> assembleExamVOList(List<Long> examIdList) {
        if (CollectionUtil.isEmpty(examIdList)) {
            //说明redis当中没数据 从数据库中查数据并且重新刷新缓存
            return null;
        }
        //拼接redis当中key的方法 并且将拼接好的key存储到一个list中
        List<String> detailKeyList = new ArrayList<>();
        for (Long examId : examIdList) {
            detailKeyList.add(getDetailKey(examId));
        }
        // 批量获取数据
        List<ExamVO> examVOList = redisService.multiGet(detailKeyList, ExamVO.class);
         // 清除掉 竞赛列表里 为null 的值
        CollUtil.removeNull(examVOList);
         // 如果 竞赛列表里有 null的值 , 就表示 redis里有数据问题
        if (CollectionUtil.isEmpty(examVOList) || examVOList.size() != examIdList.size()) {
            //说明redis中数据有问题 从数据库中查数据并且重新刷新缓存
            return null;
        }
        return examVOList;
    }

    private String getExamListKey(Integer examListType , Long userId) {
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(examListType)) {
            return CacheConstants.EXAM_UNFINISHED_LIST;
        } else if (ExamListType.EXAM_HISTORY_LIST.getValue().equals(examListType)) {
            return CacheConstants.EXAM_HISTORY_LIST;
        }else if(ExamListType.USER_EXAM_LIST.getValue().equals(examListType)){
            return CacheConstants.USER_EXAM_LIST + userId ;
        }
        return "" ;
    }
      // 获取 redis 里存储 竞赛信息的 key
    private String getDetailKey(Long examId) {
        return CacheConstants.EXAM_DETAIL + examId;
    }

    // 获取我的竞赛列表key
    private String getUserExamListKey(Long userId){
        return CacheConstants.USER_EXAM_LIST+userId;
    }

    // 获取当前用户所有的已参加的竞赛列表
    public List<Long> getAllUserExamList(Long userId) {
         String examListKey = getExamListKey( ExamListType.USER_EXAM_LIST.getValue(), userId);
         // 从redis里获取当前用户所有的 已 报名的竞赛列表
        List<Long> allUserExamList = redisService.getCacheListByRange(examListKey, 0, -1, Long.class);
       // 如果不为空
        if(CollectionUtil.isNotEmpty(allUserExamList)){
            return allUserExamList ;
        }
        // 如果为空 ,取数据库里取查找 , 同时刷新缓存
        List<UserExam> userExams = userExamMapper.selectList(new LambdaQueryWrapper<UserExam>()
                .eq(UserExam::getUserId, userId));
        // 如果数据库里也没有
        if(CollectionUtils.isEmpty(userExams)){
            return null ;
        }
         // 刷新redis 缓存
        refreshCache(ExamListType.USER_EXAM_LIST.getValue(),  userId) ;
         // 将examList 转换为只有examId的Longlist
        return userExams.stream().map(UserExam::getExamId).collect(Collectors.toList());
    }
    // 刷新 竞赛题目列表缓存
    public void refreshExamQuestionListCaChe(Long examId) {
        // 获取竞赛题目列表 , 按题目排序
        List<ExamQuestion> examQuestions = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getQuestionOrder));

        List<Long> examQuestionList = examQuestions.stream().map(ExamQuestion::getQuestionId).toList();
        //存放 到缓存里

        redisService.rightPushAll(getExamQuestionListKey(examId), examQuestionList);

        // 优化redis缓存资源 设置 竞赛题目列表缓存 有效时间为 24h
        // 获取明天0点0分0秒的时候
        long between = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                LocalDateTime.now().plusDays(1).withHour(0).withSecond(0).withNano(0));
        redisService.expire(getExamQuestionListKey(examId),between);

    }

    // 获取竞赛中的第一题
    public Long getFirstQuestion(Long examId) {
        // 获取list中指定下标的元素
        return redisService.indexForList(getExamQuestionListKey(examId),0 ,Long.class);
    }

    public Long preQuestion(Long examId, Long questionId) {
        String examQuestionListKey = getExamQuestionListKey(examId);
        // 获取当前下标
        long index = redisService.indexOfForList(examQuestionListKey, questionId);
        if(index == 0){
            throw new ServiceException(ResultCode.FAILED_FIRST_QUESTION);
        }
        // 获取指定list
      return   redisService.indexForList(examQuestionListKey , index-1 , Long.class);

    }

    public Long nextQuestion(Long examId, Long questionId) {
        String examQuestionListKey = getExamQuestionListKey(examId);
        // 获取当前下标
        long index = redisService.indexOfForList(examQuestionListKey, questionId);
        // 获取数量
        Long listSize = redisService.getListSize(examQuestionListKey) -1;
        if(index == listSize ){
            throw new ServiceException(ResultCode.FAILED_LAST_QUESTION);
        }
        // 获取指定list
        return   redisService.indexForList(examQuestionListKey , index+1 , Long.class);

    }

    public Long getRankListSize(Long examId) {
        String examRankListKey = getExamRankListKey(examId);
        return redisService.getListSize(examRankListKey);
    }
    //获取竞赛排名key
    private String getExamRankListKey(Long examId) {
        return CacheConstants.EXAM_RANK_LIST + examId;
    }

    public void refreshCache(List<ExamRankVO> examRankVOList, Long examId) {
        String examRankListKey = getExamRankListKey(examId);
        List<UserScore> userScoreList = new ArrayList<>() ;
        for (ExamRankVO examRankVO: examRankVOList){
            UserScore userScore = new UserScore();
            userScore.setExamId(examId) ;
            userScore.setExamRank(examRankVO.getExamRank());
            userScore.setScore(examRankVO.getScore());
            userScore.setUserId(examRankVO.getUserId());
           userScoreList.add(userScore);
        }
        // 先删除
        redisService.deleteObject(examRankListKey) ;
        redisService.rightPushAll(examRankListKey , userScoreList) ;
    }

    public List<ExamRankVO> getExamRankVOList(ExamRankDTO examRankDTO) {
        // 计算下标处理分页
        int start = (examRankDTO.getPageNum() - 1) * examRankDTO.getPageSize();
        int end = start + examRankDTO.getPageSize() - 1; //下标需要 -1
        // 判断是要 哪个竞赛列表
        String examRankListKey =  getExamRankListKey(examRankDTO.getExamId());

        // 获取列表 redis里的列表存储的的是竞赛id
        List<UserScore> userScoreList  = redisService.getCacheListByRange(examRankListKey, start, end, UserScore.class);
        List<ExamRankVO> examRankVOList = new ArrayList<>() ;
        for(UserScore userScore : userScoreList){
            ExamRankVO examRankVO = new ExamRankVO() ;
            BeanUtil.copyProperties(userScore ,examRankVO) ;
            UserVO userVO = userCacheManager.getUserById(userScore.getUserId());
            examRankVO.setNickName(userVO.getNickName());
            examRankVOList.add(examRankVO);
        }
        return examRankVOList;
    }

    public String getUserDetail(Long userId){
        return CacheConstants.USER_DETAIL + userId ;
    }

}